//  TwoPointsCrossover.java
//
//  Author:
//       Antonio J. Nebro <antonio@lcc.uma.es>
//       Juan J. Durillo <durillo@lcc.uma.es>
//
//  Copyright (c) 2011 Antonio J. Nebro, Juan J. Durillo
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
// 
//  You should have received a copy of the GNU Lesser General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

package etmo.operators.crossover;

import etmo.core.Solution;
import etmo.encodings.solutionType.PermutationSolutionType;
import etmo.encodings.variable.Permutation;
import etmo.util.Configuration;
import etmo.util.JMException;
import etmo.util.PseudoRandom;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

/**
 * This class allows to apply a two points crossover operator using two parent
 * solutions. NOTE: the type of the solutions must be Permutation..
 */
public class TwoPointsCrossover extends Crossover {

	/**
	 * Valid solution types to apply this operator
	 */
	private static final List VALID_TYPES = Arrays.asList(PermutationSolutionType.class);

	private Double crossoverProbability_ = null;

	/**
	 * Constructor Creates a new intance of the two point crossover operator
	 */
	public TwoPointsCrossover(HashMap<String, Object> parameters) {
		super(parameters);

		if (parameters.get("probability") != null)
			crossoverProbability_ = (Double) parameters.get("probability");
	} // TwoPointsCrossover

	/**
	 * Constructor
	 * 
	 * @param A
	 *            properties containing the Operator parameters Creates a new
	 *            intance of the two point crossover operator
	 */
	// public TwoPointsCrossover(Properties properties) {
	// this();
	// }

	/**
	 * Perform the crossover operation
	 * 
	 * @param probability
	 *            Crossover probability
	 * @param parent1
	 *            The first parent
	 * @param parent2
	 *            The second parent
	 * @return Two offspring solutions
	 * @throws JMException
	 */
	public Solution[] doCrossover(double probability, Solution parent1, Solution parent2) throws JMException {

		Solution[] offspring = new Solution[2];

		offspring[0] = new Solution(parent1);
		offspring[1] = new Solution(parent2);

		if (parent1.getType().getClass() == PermutationSolutionType.class) {
			if (PseudoRandom.randDouble() < probability) {
				int crosspoint1;
				int crosspoint2;
				int permutationLength;
				int parent1Vector[];
				int parent2Vector[];
				int offspring1Vector[];
				int offspring2Vector[];

				permutationLength = ((Permutation) parent1.getDecisionVariables()[0]).getLength();
				parent1Vector = ((Permutation) parent1.getDecisionVariables()[0]).vector_;
				parent2Vector = ((Permutation) parent2.getDecisionVariables()[0]).vector_;
				offspring1Vector = ((Permutation) offspring[0].getDecisionVariables()[0]).vector_;
				offspring2Vector = ((Permutation) offspring[1].getDecisionVariables()[0]).vector_;

				// STEP 1: Get two cutting points
				crosspoint1 = PseudoRandom.randInt(0, permutationLength - 1);
				crosspoint2 = PseudoRandom.randInt(0, permutationLength - 1);

				while (crosspoint2 == crosspoint1)
					crosspoint2 = PseudoRandom.randInt(0, permutationLength - 1);

				if (crosspoint1 > crosspoint2) {
					int swap;
					swap = crosspoint1;
					crosspoint1 = crosspoint2;
					crosspoint2 = swap;
				} // if

				// STEP 2: Obtain the first child
				int m = 0;
				for (int j = 0; j < permutationLength; j++) {
					boolean exist = false;
					int temp = parent2Vector[j];
					for (int k = crosspoint1; k <= crosspoint2; k++) {
						if (temp == offspring1Vector[k]) {
							exist = true;
							break;
						} // if
					} // for
					if (!exist) {
						if (m == crosspoint1)
							m = crosspoint2 + 1;
						offspring1Vector[m++] = temp;
					} // if
				} // for

				// STEP 3: Obtain the second child
				m = 0;
				for (int j = 0; j < permutationLength; j++) {
					boolean exist = false;
					int temp = parent1Vector[j];
					for (int k = crosspoint1; k <= crosspoint2; k++) {
						if (temp == offspring2Vector[k]) {
							exist = true;
							break;
						} // if
					} // for
					if (!exist) {
						if (m == crosspoint1)
							m = crosspoint2 + 1;
						offspring2Vector[m++] = temp;
					} // if
				} // for
			} // if
		} // if
		else {
			Configuration.logger_.severe("TwoPointsCrossover.doCrossover: invalid " + "type"
					+ parent1.getDecisionVariables()[0].getVariableType());
			Class cls = java.lang.String.class;
			String name = cls.getName();
			throw new JMException("Exception in " + name + ".doCrossover()");
		}

		return offspring;
	} // makeCrossover

	/**
	 * Executes the operation
	 * 
	 * @param object
	 *            An object containing an array of two solutions
	 * @return An object containing an array with the offSprings
	 * @throws JMException
	 */
	public Object execute(Object object) throws JMException {
		Solution[] parents = (Solution[]) object;
		Double crossoverProbability;

		if (!(VALID_TYPES.contains(parents[0].getType().getClass())
				&& VALID_TYPES.contains(parents[1].getType().getClass()))) {

			Configuration.logger_.severe("TwoPointsCrossover.execute: the solutions "
					+ "are not of the right type. The type should be 'Permutation', but " + parents[0].getType()
					+ " and " + parents[1].getType() + " are obtained");
		} // if

		crossoverProbability = (Double) getParameter("probability");

		if (parents.length < 2) {
			Configuration.logger_.severe("TwoPointsCrossover.execute: operator needs two " + "parents");
			Class cls = java.lang.String.class;
			String name = cls.getName();
			throw new JMException("Exception in " + name + ".execute()");
		}

		Solution[] offspring = doCrossover(crossoverProbability_, parents[0], parents[1]);

		return offspring;
	} // execute

} // TwoPointsCrossover
